﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace TotalFreedomAngularJS
{
    public static class AjHelperExtensions
    {
        #region Controling text and appearance

        public static MvcHtmlString AjText<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            if (htmlHelper == null)
                return null;

            string expr = "";

            if (expression != null)
            {
                PropertyData data = AjHelperExtensions.GetPropertyData(expression);
                if (data != null && !String.IsNullOrEmpty(data.Expression))
                    expr = HttpUtility.HtmlEncode(data.Expression);
            }

            string s = "{{" + expr + "}}";

            return new MvcHtmlString(s);
        }

        #endregion

        public static PropertyData GetPropertyData<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression, bool onlyProperty = false, string propertyNamePrefix = null)
        {
            PropertyData data = new PropertyData();


            if (expression.Body.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression memberExpression = (MemberExpression)expression.Body;
                data.PropertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
                Type containerType = memberExpression.Expression.Type;
                PropertyDescriptorCollection coll = TypeDescriptor.GetProperties(containerType);
                PropertyDescriptor descr = coll[data.PropertyName];
                if (descr != null)
                {
                    AttributeCollection attributes = descr.Attributes;
                    if (attributes != null)
                    {
                        DescriptionAttribute descrAttribute = (DescriptionAttribute)attributes[typeof(DescriptionAttribute)];
                        if (descrAttribute != null)
                            data.Description = descrAttribute.Description;
                    }
                }
            }
            else
            {
                if (onlyProperty)
                {
                    throw (new NotSupportedException("Only property supported as lambda expression for the attribute"));
                }

                data.Expression = GetLambda(expression.Body, propertyNamePrefix);
            }

            return data;
        }

        private static string GetLambda(System.Linq.Expressions.Expression expression, string propertyNamePrefix = null)
        {
            if (expression == null)
                return "";

            switch (expression.NodeType)
            {
                // for now no convert processing, let javascript take care of it
                case ExpressionType.Convert:
                    return GetLambda(((UnaryExpression)expression).Operand, propertyNamePrefix);

                case ExpressionType.MemberAccess:
                    MemberExpression memberExpression = (MemberExpression)expression;
                    return memberExpression.Member is PropertyInfo ? (propertyNamePrefix != null ? propertyNamePrefix : "") + memberExpression.Member.Name + "()" : null;

                case ExpressionType.ArrayLength:
                    return GetLambda(((UnaryExpression)expression).Operand, propertyNamePrefix) + ".length";

                case ExpressionType.Call:
                    MethodCallExpression methodExpr = (MethodCallExpression)expression;
                    return methodExpr.Method.Name + "()";

                case ExpressionType.Conditional:
                    ConditionalExpression condExpr = (ConditionalExpression)expression;
                    string test = GetLambda(condExpr.Test, propertyNamePrefix);
                    string ifTrue = GetLambda(condExpr.IfTrue, propertyNamePrefix);
                    string ifFalse = GetLambda(condExpr.IfFalse, propertyNamePrefix);
                    return " " + test + " ? " + ifTrue + " : " + ifFalse + " ";

                case ExpressionType.Constant:
                    return GetJavaScriptConstant(((ConstantExpression)expression).Value);

                case ExpressionType.Not:
                case ExpressionType.Negate:
                    return "!" + GetLambda(((UnaryExpression)expression).Operand);

                case ExpressionType.OrElse:
                case ExpressionType.AndAlso:
                    {
                        BinaryExpression binaryExpr = (BinaryExpression)expression;

                        string left = GetLambda(binaryExpr.Left, propertyNamePrefix);
                        string right = GetLambda(binaryExpr.Right, propertyNamePrefix);
                        return "(" + left + (expression.NodeType == ExpressionType.OrElse ? " || " : " && ") + right + ")";
                    }

                case ExpressionType.Add:
                case ExpressionType.Divide:
                case ExpressionType.Multiply:
                case ExpressionType.Subtract:
                case ExpressionType.And:
                case ExpressionType.Or:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.NotEqual:
                case ExpressionType.Equal:
                    {
                        BinaryExpression binaryExpr = (BinaryExpression)expression;

                        string left = GetLambda(binaryExpr.Left, propertyNamePrefix);
                        string right = GetLambda(binaryExpr.Right, propertyNamePrefix);
                        string oper = GetOperand(expression.NodeType);


                        return left + " " + oper + " " + right;
                    }

                default:
                    throw (new Exception("Please implement this angularjs feature"));
            }
        }

        private static string GetOperand(ExpressionType expressionType)
        {
            switch (expressionType)
            {
                case ExpressionType.AndAlso:
                    return "&&";

                case ExpressionType.And:
                    return "&";

                case ExpressionType.OrElse:
                    return "||";

                case ExpressionType.Or:
                    return "|";

                case ExpressionType.Equal:
                    return "==";

                case ExpressionType.NotEqual:
                    return "!=";

                case ExpressionType.Negate:
                    return "!";

                case ExpressionType.GreaterThan:
                    return ">";

                case ExpressionType.GreaterThanOrEqual:
                    return ">=";

                case ExpressionType.LessThan:
                    return "<";

                case ExpressionType.LessThanOrEqual:
                    return "<=";

                case ExpressionType.Add:
                    return " + ";

                case ExpressionType.Subtract:
                    return " - ";

                case ExpressionType.Multiply:
                    return " * ";

                case ExpressionType.Divide:
                    return " / ";

                default:
                    throw (new Exception("Please implement this angularjs feature"));
            }
        }

        private static string GetJavaScriptConstant(object value)
        {
            if (value == null)
                return "null";

            if (value is bool)
                return ((bool)value) ? "true" : "false";

            if (value is int)
                return "" + ((int)value);

            if (value is double)
                return "" + ((double)value);

            return "'" + value.ToString() + "'";
        }

    }
}
